package plateau;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import structureDeDonnee.Cell;
import structureDeDonnee.Matrice;
import structureDeDonnee.Position;

/**
 * class implémentant plateau avec les règles d'un monde circulaire. C'est à
 * dire: dont les extremités se rejoignent.
 */
public class PlateauCirculaire extends PlateauFini {

	/**
	 * constructeur
	 * 
	 * @param matrice
	 *            matrice des cellules vivantes (on attend une matrice trié)
	 * @param valeursDeSurvie
	 * @param valeursDeNaissance
	 * @param x
	 *            largeur du plateau
	 * @param y
	 * @throws CelluleHorsPlateauException
	 *             est levé si une cellule de la matrice est en dehors du
	 *             plateau
	 */
	public PlateauCirculaire(Matrice matrice, int[] valeursDeSurvie,
			int[] valeursDeNaissance, int y, int x)
			throws CelluleHorsPlateauException {
		super(matrice, valeursDeSurvie, valeursDeNaissance, y, x);
	}

	/**
	 * constructeur
	 * 
	 * @param plateau
	 *            plateauFini dont on recupere les dimention et la matrice
	 */
	public PlateauCirculaire(PlateauFini plateau) {
		super(plateau);
	}

	@Override
	public boolean estUneTranslation(Matrice matriceAComparer, Position p) {
		assert (!matriceAComparer.isEmpty());
		if (matriceAComparer.length() == this.matrice.length()) {

			List<Position> linkList = generateLinkList(this.matrice);
			Iterator<Cell> itr = matriceAComparer.iterator();
			while (itr.hasNext()) {
				Cell c = itr.next();
				if (testLink(c, linkList, matriceAComparer)) {
					Position tmp = matrice.getPremiereCellule().getPosition();
					p.setX(mod(c.getPosition().getX() - tmp.getX(), width));
					p.setY(mod(c.getPosition().getY() - tmp.getY(), height));
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * @param point
	 *            coordonné d'un point
	 * @param vector
	 *            coordonné d'un vecteur
	 * @return le point translaté d'un vecteur
	 */
	private Position translation(Position point, Position vector) {
		int newPosX = mod(point.getX() + vector.getX(), this.width);
		int newPosY = mod(point.getY() + vector.getY(), this.height);

		return new Position(newPosX, newPosY);
	}

	/**
	 * @param x
	 * @param n
	 * @return x%n
	 */
	private int mod(int x, int n) {
		assert (n > 0);
		if (x < 0) {
			return mod(x + n, n);
		} else {
			return x % n;
		}
	}

	/**
	 * verifie si la liste des cellules obtenus en applicant une serie de
	 * translation sur une cellules correspond à l'ensemble des cellules vivante
	 * d'une matrice
	 * 
	 * @param c
	 *            cellule
	 * @param linkList
	 *            liste de vecteurs
	 * @param m
	 *            matrice
	 * @return vrai si la listes des cellules obtenus correspond aux cellules de
	 *         la matrice, faux sinon
	 */
	private boolean testLink(Cell c, List<Position> linkList, Matrice m) {
		Cell tmp;
		for (Position p : linkList) {
			tmp = new Cell(translation(c.getPosition(), p));
			if (!m.contains(tmp)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * genere la liste des vecteurs entre la cellules la plus en haut à gauche
	 * de la matrice envoyé en paramètre et toute les autres cellules de la
	 * matrice envoyé en paramètre
	 * 
	 * @param livingCells
	 *            matrice comprenant une liste de cellules
	 * @return une liste de vecteurs
	 */
	private List<Position> generateLinkList(Matrice livingCells) {
		Iterator<Cell> it = livingCells.iterator();
		Position reference = it.next().getPosition();
		ArrayList<Position> listToBeReturn = new ArrayList<Position>();
		Cell tmp = null;
		Position tmp2;

		while (it.hasNext()) {
			tmp = it.next();
			tmp2 = tmp.getPosition();
			listToBeReturn.add(new Position(tmp2.getX() - reference.getX(),
					tmp2.getY() - reference.getY()));
		}
		return listToBeReturn;
	}

	@Override
	public PlateauCirculaire clone() {
		return new PlateauCirculaire(this);
	}

	@Override
	public void ajouteCelluleMatricesVoisines(Cell tmp, Matrice[] tab) {
		int posXCell = tmp.getPosition().getX();
		int posYCell = tmp.getPosition().getY();
		
		//123
		//456
		//789
		Cell tmp1 = new Cell(1, mod((posXCell - 1), width), mod((posYCell + 1), height));
		Cell tmp2 = new Cell(1, mod((posXCell), width), mod(posYCell + 1, height));
		Cell tmp3 = new Cell(1, mod((posXCell + 1), width), mod(posYCell + 1 , height));
		Cell tmp4 = new Cell(1, mod((posXCell - 1), width), mod(posYCell , height));
		Cell tmp5 = new Cell(9, posXCell, posYCell);
		Cell tmp6 = new Cell(1, mod((posXCell + 1), width), mod(posYCell, height));
		Cell tmp7 = new Cell(1, mod((posXCell - 1),width), mod(posYCell - 1, height));
		Cell tmp8 = new Cell(1, mod((posXCell), width), mod(posYCell - 1 , height));
		Cell tmp9 = new Cell(1, mod((posXCell + 1), width), mod (posYCell - 1, height));
		
		// pour les cellules sur les bords
		//la cellule à ajouter risque de ne pas
		// se retrouver en fin de liste (particularité du 
		//plateau circulaire: le voisin du haut d'une cellule tout 
		//en haut du plateau est tout en bas)
		//on va donc utilisé une fonction qui verifie que l'ajout
		//maintien la matrice triée  
		
		ajouterCelluleMatrice(tmp1,tab[0]);
		
		ajouterCelluleMatrice(tmp2,tab[1]);
		ajouterCelluleMatrice(tmp3,tab[2]);
		ajouterCelluleMatrice(tmp4,tab[3]);
		tab[4].add(tmp5);
		ajouterCelluleMatrice(tmp6,tab[5]);
		ajouterCelluleMatrice(tmp7,tab[6]);
		ajouterCelluleMatrice(tmp8,tab[7]);
		ajouterCelluleMatrice(tmp9,tab[8]);
	}

	/**
	 * ajoute cellule dans une matrice sans casser le tri de la matrice.
	 * La plupart du temps la cellule doit juste être ajouté à la fin
	 * de la matrice et ça se fait en O(1) 
	 * mais si la cellule doit être ajouté autre part
	 * l'insertion est au pire des cas en O(n)
	 * 
	 * @param cell cellule à ajouter
	 * @param m matrice où ajouter la matrice 
	 */
	private void ajouterCelluleMatrice(Cell cell,Matrice m){
		Cell derniere = m.getDerniereCellule();
		
		if(derniere == null || derniere.compareTo(cell)>=0){
			m.add(cell); //on ajoute en fin de liste ( O(1) )
		}
		else {
			m.inserer(cell); //on ajoute au bon endroit (au pire des cas O(n))
		}
	}

}
